<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>map2D</title>

    <style>
        body {
            margin: 0px;
            background-color: #000000;
            overflow: hidden;
        }
    </style>
</head>

<script src="lib/three.js"></script>
<script src="lib/CameraController.js"></script>

<body oncontextmenu=self.event.returnValue=false>
    <div style="user-select: none;">

    </div>
</body>
    <script>

        let allLength = 20037508.3427892*2;

        let camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight, 1, allLength );
        let position = new THREE.Vector3(allLength/2, allLength/2, allLength/2);
        camera.position.copy(position);
        let target = new THREE.Vector3( position.x, 0, position.z);
        camera.lookAt(target);

        let control = new CameraController(camera);
        control.update();

        scene = new THREE.Scene();

        let renderer = new THREE.WebGLRenderer( { antialias: true } );
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );
        window.addEventListener( 'resize', onWindowResize, false );

        let loader = new THREE.TextureLoader();

        function onWindowResize() {

            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize( window.innerWidth, window.innerHeight );
        }

        let allTile = {};
        let loadingTile = {};

        function addTile(x,y,z,mesh, allTile) {
            if(!allTile[z]){
                allTile[z] = {};
            }
            if(!allTile[z][x]){
                allTile[z][x] = {};
            }

            allTile[z][x][y] = mesh;
        }

        function findTile(x,y,z, allTile) {
            if(!allTile[z]){
                return null;
            }
            if(!allTile[z][x]){
                return null;
            }

            return allTile[z][x][y];
        }

        function removeLoadingTile(x,y,z) {
            if(!loadingTile[z]){
                return;
            }
            if(!loadingTile[z][x]){
                return;
            }

            delete loadingTile[z][x][y];
        }

        function removeOtherTileZ(z) {
            for(let key0 in allTile){
                let key = key0-0;
                if(key !== z){
                    for(let x in allTile[key]){
                        for(let y in allTile[key][x]){
                            if(allTile[key][x][y]){
                                scene.remove(allTile[key][x][y]);
                            }
                        }
                    }
                    delete allTile[key];
                }
            }
        }

        function removeOldTile(x,y,z) {
            for(let z1 in allTile){
                 for(let x1 in allTile[z1]){
                     for(let y1 in allTile[z1][x1]){
                         x1-=0;
                         y1-=0;
                         z1-=0;
                         let overlap;
                         if(z>z1){
                             overlap = checkTwoTile(x,y,z, x1,y1,z1);
                         }else{
                             overlap = checkTwoTile(x1,y1,z1, x,y,z);
                         }
                         if(overlap){
                             scene.remove(allTile[z1][x1][y1]);
                             delete allTile[z1][x1][y1];
                         }
                     }
                 }
            }
        }

        function checkTwoTile(x,y,z, x1,y1,z1){
            let count = Math.pow(2, z);
            let tileWidth = allLength/count;

            let top = y*tileWidth;
            let bottom = y*tileWidth+tileWidth;
            let left = x*tileWidth;
            let right = x*tileWidth+tileWidth;
            let centerX = (left + right)/2;
            let centerY = (top + bottom)/2;

            let count1 = Math.pow(2, z1);
            let tileWidth1 = allLength/count1;

            let top1 = y1*tileWidth1;
            let bottom1 = y1*tileWidth1+tileWidth1;
            let left1 = x1*tileWidth1;
            let right1 = x1*tileWidth1+tileWidth1;

            if(centerX>left1 && centerX<right1 && centerY>top1 && centerY<bottom1){
                return true;
            }
            return false;
        }

        function checkTile(zTile) {
            let z = Math.ceil(Math.log2(allLength/camera.position.y));
            return z === zTile;
        }

        function updateTiles() {

            let z = Math.ceil(Math.log2(allLength/camera.position.y));

            let rect = {
                top: camera.position.z - camera.position.y,
                bottom: camera.position.z + camera.position.y,
                left: camera.position.x - camera.position.y/window.innerHeight*window.innerWidth,
                right: camera.position.x + camera.position.y/window.innerHeight*window.innerWidth,
            }

            let count = Math.pow(2, z);
            let tileWidth = allLength/count;

            let x0 = Math.floor(rect.left/tileWidth);
            let x1 = Math.floor(rect.right/tileWidth);

            let y0 = Math.floor(rect.top/tileWidth);
            let y1 = Math.floor(rect.bottom/tileWidth);

            for(let x=x0; x<=x1; x++){
                if(x<0 || x>count-1){
                    continue;
                }
                for(let y=y0; y<=y1; y++){
                    if(y<0 || y>count-1){
                        continue;
                    }
                    getOneTile(x,y,z,tileWidth);
                }
            }

        }

        function getOneTile(x,y,z,tileWidth) {
            //谷歌影像 已被墙
            // let url = 'http://mt1.google.cn/vt/lyrs=s&hl=zh-CN&x='+x+'&y='+y+'&z='+z+'&s=Gali';
            //osm 不全
            // let url = 'https://tile.openstreetmap.org/'+z+'/'+x+'/'+y+'.png';
            //mapbox 慢
            let url = 'https://api.mapbox.com/v4/mapbox.satellite/'+z+'/'+x+'/'+y+'.webp?sku=101iGH34Pnrym&access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4M29iazA2Z2gycXA4N2pmbDZmangifQ.-g_vE53SD2WrJ6tFX7QHmA';

            let isLoading = findTile(x,y,z, loadingTile);
            let isAll = findTile(x,y,z, allTile);
            if(isLoading || isAll){
                return;
            }

            addTile(x,y,z,true,loadingTile);

            let map = loader.load(
                url,

                // onLoad回调
                function ( texture ) {

                    if(checkTile(z)){
                        let material = new THREE.MeshBasicMaterial( {
                            map: texture
                        } );
                        let geometry = new THREE.PlaneGeometry( tileWidth, tileWidth);
                        geometry.rotateX(-Math.PI/2);
                        geometry.translate(tileWidth/2,0,tileWidth/2);
                        let mesh = new THREE.Mesh( geometry, material );
                        mesh.position.set( x*tileWidth, 0, y*tileWidth );
                        scene.add( mesh );

                        removeOldTile(x,y,z);
                        addTile(x,y,z,mesh,allTile);
                    }

                    removeLoadingTile(x,y,z);

                },

                // 目前暂不支持onProgress的回调
                undefined,

                // onError回调
                function ( err ) {
                    console.error( '没有此瓦片：' + url );
                }
            );

        }

        updateTiles();
        function animate() {

            requestAnimationFrame( animate );
            updateTiles();
            renderer.render( scene, camera );
        }

        animate();

    </script>

</html>